<?php
declare (strict_types=1);

namespace app\api\controller\admin\setting;

use app\api\common\controller\BaseApi;
use app\Request;
use think\App;
use think\facade\Config;
use think\captcha\facade\Captcha;
use Emarref\Jwt\Claim;
use Emarref\Jwt\Token;
use Emarref\Jwt\Jwt;
use Emarref\Jwt\Algorithm;
use Emarref\Jwt\Encryption\Factory;
use Emarref\Jwt\Verification\Context;

class LoginController extends BaseApi
{

    /**
     * login
     *
     * @param \app\Request $request
     *
     * @return \think\response\Json
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function login(Request $request): \think\response\Json
    {
        $data = $request->getParams([
            ['account', '', 'trim'],
            ['password', ''],
            ['captcha'],
            ['cid'],
        ]);

        $this->validate($data, [
            'account'  => 'require|alphaNum|length:4,32',
            'password' => 'require|length:6,32',
            'captcha'  => 'require|alphaNum',
            'cid'      => 'require',
        ]);

        if (!$this->verifyCaptcha($data['captcha'], $data['cid'])) {
            return ret_value(-1, "验证码错误");
        }

//        $lms_common = Config::get('lms_common');
        $user = \think\facade\Db::connect('mysql')
            ->name('admin_user')
            ->field('user.*, role.role_name, role.rules, role.status AS role_status')
            ->alias("user")
            ->leftJoin('admin_role role', 'user.roles = role.id')
            ->where('account', $data['account'])
//            ->where('pwd', $password)
            ->where('user.delete_time', null)
            ->where('role.delete_time', null)
            ->where('role.status', 1)
            ->find();

        if (empty($user))
            return ret_value(-2, "用户名不存在");

        $password = md5($user["salt"] . $data['password']);
        if ($password !== $user['pwd'])
            return ret_value(-2, "用户密码错误");

        if ($user['status'] !== 1)
            return ret_value(-3, "账户状态异常");

        $usertoken = ["id" => $user["id"], "roles" => $user["roles"], "level" => $user['level']];
        $userinfo  = ["account"   => $user["account"],
                      "id"        => $user["id"],
                      "real_name" => $user["real_name"],
                      "roles"     => $user["roles"],
                      "level"     => $user["level"],
                      "role_name" => $user["role_name"],
                      //"avatar" => $user["avatar"],
        ];

        $rules = \think\facade\Db::connect('mysql')
            ->name('admin_permission')
            ->when($user["level"] > 0, function ($query) use ($user) {
                $query->whereIn('id', $user['rules']);
            })
            ->where('delete_time', null)
            ->where('status', 1)
            ->order('sort', 'asc')
            ->select();

        return ret_value(0, "success", ['token' => $this->createToken($data['cid'], $usertoken), 'info' => $userinfo, 'rules' => $rules]);
    }

    //生成Token，jwtID为客户端的 验证码ID（也可以服务端提供）
    public function verifyAuth(Request $request)
    {
        //echo( $request->param("token"));
        //echo( $request["get"]("token"));
        //echo( $request->post("token"));
        //echo( "request->get");

        return $this->verifyToken($request->header("Authorization"));
        //return this->verifyToken($request->header("token"));
        //$token = $jwt->deserialize($request->param("token"));
    }

    /**
     * createToken 生成Token，jwtID为客户端的 验证码ID（也可以服务端提供）
     *
     * @param $jwtID
     * @param $dataClaim
     *
     * @return string
     */
    protected function createToken($jwtID, $dataClaim): string
    {
        $lms_config = Config::get('lms_common');
        $token      = new Token();
        $jwt        = new Jwt();

        //给Payload设置数据（Claim）
        $token->addClaim(new Claim\Expiration(new \DateTime('+1 days')));
        $token->addClaim(new Claim\IssuedAt(new \DateTime('now')));
        $token->addClaim(new Claim\Issuer($lms_config['JWT_Issuser']));
        $token->addClaim(new Claim\JwtId(uniqid((string)$jwtID, true)));    //session_create_id()
        $token->addClaim(new Claim\NotBefore(new \DateTime('now')));
        $token->addClaim(new Claim\Subject($lms_config['JWT_Subject']));

        // Custom claims are supported
        $token->addClaim(new Claim\PublicClaim('refresh_token_expire', (new \DateTime('+3 days'))->getTimestamp()));
        $token->addClaim(new Claim\PrivateClaim('data', $dataClaim));

        //加密
        $algorithm  = new Algorithm\Hs256($lms_config['JWT_Password']);
        $encryption = Factory::create($algorithm);
        return $jwt->serialize($token, $encryption);
    }

    /**
     * verifyToken  验证Token，过期则重新生成新Token，否则重新登录
     *
     * @param $token
     *
     * @return \think\response\Json
     */
    protected function verifyToken($token): \think\response\Json
    {
        $lms_config = Config::get('lms_common');

        $jwt = new Jwt();

        $algorithm  = new Algorithm\Hs256($lms_config['JWT_Password']);
        $encryption = Factory::create($algorithm);
        $context    = new Context($encryption);
        $context->setIssuer($lms_config['JWT_Issuser']);
        $context->setSubject($lms_config['JWT_Subject']);
        $token = $jwt->deserialize($token);    //获取token并反序列

        $httpCode = 200;
        $retCode  = 0;
        $retMsg   = "";

        try {

            $payload              = ($token->getPayload());    //获取Payload
            $exp                  = ($payload->findClaimByName("exp")->getValue());    //过期时间
            $refresh_token_expire = ($payload->findClaimByName("refresh_token_expire")->getValue());    //刷新时间

            $now = time();
            if ($exp < $now) {
                $httpCode = 401;

                //刷新时间也过期，则需要重新登录
                if ($refresh_token_expire < $now) {
                    $retCode = -1;
                } else {    //重新生成Token

                    return ret_value($retCode, "refreshToken", $this->createToken(
                    //session_create_id(),
                        $payload->findClaimByName("jti")->getValue(),    //jti也用原来的，最好用新的
                        $payload->findClaimByName("data")->getValue()    //数据还是原来的
                    ), $httpCode);
                }
            }

            //系统验证Token
            $jwt->verify($token, $context);
        } catch (\Emarref\Jwt\Exception\InvalidAudienceException $e) {
            $httpCode = 401;
            $retCode  = -2;
            $retMsg   = $e->getMessage();
        } catch (\Emarref\Jwt\Exception\VerificationException $e) {
            $httpCode = 401;
            $retCode  = -3;
            $retMsg   = $e->getMessage();
        } catch (\RuntimeException $e) {
            $httpCode = 401;
            $retCode  = -4;
            $retMsg   = $e->getMessage();
        } catch (\Exception $e) {
            $httpCode = 401;
            $retCode  = -5;
            $retMsg   = $e->getMessage();
        }
        return ret_value($retCode, $retMsg, null, $httpCode);
    }

    /**
     * createCaptcha 创建验证码
     *
     * @return \think\response\Json
     */
    public function createCaptcha(): \think\response\Json
    {
        $lms_common = Config::get('lms_common');
        $cid        = uniqid((string)rand(00000, 99999));
        $res        = Captcha::create();
        $key        = session('captcha.key');
        $img        = "data:image/png;base64," . base64_encode($res->getData());
        cache($lms_common['project_name'] . $cid, $key);
        return json(['img' => $img, 'key' => $cid]);
    }

    /**
     * verifyCaptcha 验证码验证成功后删除
     *
     * @param $c
     * @param $k
     *
     * @return bool
     */
    protected function verifyCaptcha($c, $k): bool
    {
        $lms_common = Config::get('lms_common');
        if (is_null($c))
            return false;
        $key = cache($lms_common['project_name'] . $k);
        if ($key && password_verify(mb_strtolower($c, 'UTF-8'), $key)) {
            cache($lms_common['project_name'] . $k, null);
            return true;
        } else {
            return false;
        }
    }

}
